package com.boot.web.controller;

import cn.hutool.core.util.IdUtil;
import com.boot.web.config.ResourceProperties;
import com.boot.web.exception.GlobalException;
import com.boot.web.http.HttpModel;
import com.boot.web.http.Status;
import com.boot.web.util.HttpResponseUtil;
import com.boot.web.util.HttpUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @description 文件传输
 * @date 2023/1/8 23:29
 **/

@RestController
@RequestMapping("/file")
public class FileController {

    private final ResourceProperties resourceProperties;

    @Data
    @SuperBuilder
    @EqualsAndHashCode
    @NoArgsConstructor
    private static class FileDto {
        String prefix;

        String fileName;
    }

    public FileController(ResourceProperties resourceProperties) {
        this.resourceProperties = resourceProperties;
    }

    @PostMapping
    public ResponseEntity<HttpModel<FileDto>> upload(@RequestParam(value = "name", required = false) String name,
                                                     @RequestPart("file") MultipartFile file) {

        String fileName = file.getOriginalFilename();
        assert fileName != null;
        String suffix = fileName.substring(fileName.lastIndexOf("."));


        fileName = name == null ? IdUtil.fastSimpleUUID() + suffix : name + '.' + IdUtil.fastSimpleUUID() + suffix;
        File dest = new File(resourceProperties.getSafeOpenRoute() + fileName);
        try {
            file.transferTo(dest);
            return HttpResponseUtil.CREATED("文件上传成功", FileDto.builder()
                    .prefix(resourceProperties.getSafeOpenRoute())
                    .fileName(fileName).build());
        } catch (IOException e) {
            return HttpResponseUtil.serverError("服务器状态异常，文件保存失败");
        }
    }

    @DeleteMapping(value = "/{fileName}")
    @ResponseBody
    public ResponseEntity<HttpModel<Object>> delete(@PathVariable String fileName) {
        File file = new File(resourceProperties.getSafeOpenRoute() + fileName);
        if (!file.exists()) {
            throw new GlobalException(Status.NO_RESOURCE, HttpStatus.NOT_FOUND);
        }
        if (file.delete()) {
            return HttpResponseUtil.OK("资源删除完成");
        }
        return HttpResponseUtil.badRequest("资源删除完成");
    }

    @GetMapping(value = "download", produces = "application/octet-stream;charset=UTF-8")
    public void download(@RequestParam("target") String target,
                         @RequestParam("fileName") String fileName) {

        HttpServletRequest request = HttpUtil.getRequest();
        HttpServletResponse response = HttpUtil.getResponse();

        File file = new File(resourceProperties.getSafeOpenRoute() + fileName);
        if (!file.exists()) {
            throw new GlobalException(Status.NO_RESOURCE, HttpStatus.NOT_FOUND);
        } else {
            response.reset();
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("UTF-8");
            try {
                response.addHeader("Content-Disposition",
                        "attachment;filename*=UTF-8''" + URLEncoder.encode(target, StandardCharsets.UTF_8.name()));
            } catch (UnsupportedEncodingException e) {
                throw new GlobalException(Status.NOT_CONVERT, "无法对文件名进行UTF-8编码", HttpStatus.BAD_REQUEST);
            }
            response.addHeader("Content-Length", "" + file.length());
            response.setHeader("Accept-Ranges", "bytes");
            long downloadSize = file.length();
            long fromPos = 0, toPos = 0;

            String range = request.getHeader("Range");
            if (null == range) {
                response.setHeader("Content-Length", downloadSize + "");
            } else {
                // 若客户端传来Range，说明之前下载了一部分，设置206状态(SC_PARTIAL_CONTENT)
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                String bytes = range.replaceAll("bytes=", "");
                String[] ary = bytes.split("-");
                fromPos = Long.parseLong(ary[0]);
                //Range字段的值有两个
                final int rangeArgs = 2;
                if (ary.length == rangeArgs) {
                    toPos = Long.parseLong(ary[1]);
                }
                long size;
                if (toPos > fromPos) {
                    size = toPos - fromPos;
                } else {
                    size = downloadSize - fromPos;
                }
                response.setHeader("Content-Length", size + "");
                downloadSize = size;
            }

            try (RandomAccessFile in = new RandomAccessFile(file, "rw");
                 ServletOutputStream out = response.getOutputStream()) {
                // 设置下载起始位置
                if (fromPos > 0) {
                    in.seek(fromPos);
                }
                // 缓冲区大小
                int bufLen = (int) (downloadSize < 2048 ? downloadSize : 2048);
                byte[] buffer = new byte[bufLen];
                int num;
                int count = 0;
                // 当前写到客户端的大小
                while ((num = in.read(buffer)) != -1) {
                    out.write(buffer, 0, num);
                    count += num;
                    //处理最后一段，计算不满缓冲区的大小
                    if (downloadSize - count < bufLen) {
                        bufLen = (int) (downloadSize - count);
                        if (bufLen == 0) {
                            break;
                        }
                        buffer = new byte[bufLen];
                    }
                }
                response.flushBuffer();
            } catch (IOException e) {
                throw new GlobalException(Status.STATE_ERROR, "文件操作失败", HttpStatus.BAD_REQUEST);
            }
        }
    }

}
